#include "stdafx.h"
#ifdef LIT_OGRE_SUPPORT
#include <LitMeshBuilder.h>

namespace Lit
{
	MeshDeclaration::Vertex::Vertex(){
		position = Lit::Vec3::ZERO;
		normal = std::make_pair( Lit::Vec3::ZERO, false );
		tangent = std::make_pair( Lit::Vec3::ZERO, false );
	}
	MeshDeclaration::Vertex::Vertex( const Lit::Vec3 &pos, const Lit::Vec3& norm, const Lit::Vec3& tang)
	{
		position=pos;
		normal = std::make_pair( norm, norm!=Lit::Vec3::ZERO );
		tangent = std::make_pair( tang, tang!=Lit::Vec3::ZERO );
	}
	MeshDeclaration::Element::Element( Ogre::VertexElementType type_, Ogre::VertexElementSemantic semantic_ )
		: type(type_), semantic(semantic_){}
	MeshDeclaration::Element::Element( const Element& rk )
		: type(rk.type), semantic(rk.semantic), offset(rk.offset){}
	MeshDeclaration::Element::~Element(){}

	void MeshDeclaration::Element::setOffset( size_t off ){ offset = off; }
	size_t MeshDeclaration::Element::getTypeSize() const{ return Ogre::VertexElement::getTypeSize( type ); }

	MeshDeclaration::MeshDeclaration( const String &name ) 
		: m_offset( 0 ), m_name( name )
	{}
	MeshDeclaration::MeshDeclaration( const MeshDeclaration& rk ) 
		: m_verts( rk.m_verts ), m_name(rk.m_name), m_inds( rk.m_inds ), m_offset( rk.m_offset ), m_list( rk.m_list )
	{}
	MeshDeclaration::~MeshDeclaration()
	{ cleanup(); }

	const String &MeshDeclaration::getName() const{ return m_name; }

	void MeshDeclaration::uploadElements(Ogre::VertexDeclaration *decl){
		if( !decl ) return;
		for( auto i=m_list.begin(); i!=m_list.end(); i++ )
			decl->addElement( 0, i->offset, i->type, i->semantic );
	}
	MeshDeclaration* MeshDeclaration::addVertex( const Vertex& vert )
	{
		m_verts.push_back( vert );
		return this;
	}
	MeshDeclaration* MeshDeclaration::addIndex( uint16_t id )
	{
		m_inds.push_back( id );
		return this;
	}
	MeshDeclaration* MeshDeclaration::addElement( const Element& elem )
	{
		Element e( elem );
		e.setOffset( m_offset );
		m_list.push_back( e );
		m_offset += m_list.back().getTypeSize();
		return this;
	}
	void MeshDeclaration::cleanup()
	{
		m_list.clear();
		m_inds.clear();
		m_verts.clear();
	}
	size_t MeshDeclaration::getNumElements() const{ return m_list.size(); }
	bool MeshDeclaration::hasElements() const{ return !m_list.empty(); }
			
	size_t MeshDeclaration::getNumVertices() const{ return m_verts.size(); }
	bool MeshDeclaration::hasVertices() const{ return !m_verts.empty(); }

	size_t MeshDeclaration::getNumIndices() const{ return m_inds.size(); }
	bool MeshDeclaration::hasIndices() const{ return !m_inds.empty(); }
			
	uint16_t& MeshDeclaration::getIndex( size_t id ){ assert(id<m_list.size()); return m_inds.at(id); }
	const uint16_t& MeshDeclaration::getIndex( size_t id ) const{ assert(id<m_list.size()); return m_inds.at(id); }

	MeshDeclaration::Element& MeshDeclaration::getElement( size_t id ){ assert(id<m_list.size()); return m_list.at(id); }
	const MeshDeclaration::Element& MeshDeclaration::getElement( size_t id ) const{ assert(id<m_list.size()); return m_list.at(id); }

	MeshDeclaration::Vertex& MeshDeclaration::getVertex( size_t id ){ assert(id<m_list.size()); return m_verts.at(id); }
	const MeshDeclaration::Vertex& MeshDeclaration::getVertex( size_t id ) const{ assert(id<m_list.size()); return m_verts.at(id); }
			
	MeshDeclaration::ElementList& MeshDeclaration::getElements(){ return m_list; }
	MeshDeclaration::VertexList& MeshDeclaration::getVertices(){ return m_verts; }
	MeshDeclaration::IndexList& MeshDeclaration::getIndices(){ return m_inds; }
	const MeshDeclaration::ElementList& MeshDeclaration::getElements()const { return m_list; }
	const MeshDeclaration::VertexList& MeshDeclaration::getVertices()const { return m_verts; }
	const MeshDeclaration::IndexList& MeshDeclaration::getIndices()const { return m_inds; }

	size_t MeshDeclaration::getOffset() const
	{
		return m_offset;
	}

	MeshBuilder::MeshBuilder( )
	{}
	MeshBuilder::~MeshBuilder(){ }
			
	Ogre::MeshPtr MeshBuilder::create( MeshDeclarationPtr decl, const String &res_grp )
	{
		if( !decl ) return Ogre::MeshPtr();
		Ogre::MeshPtr mMesh = Ogre::MeshManager::getSingleton().createManual(decl->getName(), res_grp);
		/* create the vertex data structure */
		mMesh->sharedVertexData = new Ogre::VertexData;
		mMesh->sharedVertexData->vertexCount = decl->getNumVertices();

		/* declare how the vertices will be represented */
		decl->uploadElements(mMesh->sharedVertexData->vertexDeclaration);

		/* create the vertex buffer */
		Ogre::HardwareVertexBufferSharedPtr vertexBuffer = Ogre::HardwareBufferManager::getSingleton().
			createVertexBuffer(
				decl->getOffset(), 
				mMesh->sharedVertexData->vertexCount, 
				Ogre::HardwareBuffer::HBU_STATIC
			);

		/* lock the buffer so we can get exclusive access to its data */
		float *vertices = static_cast<float *>(vertexBuffer->lock(Ogre::HardwareBuffer::HBL_NORMAL));
		Lit::Vec3 min(999999.99f), max(-999999.99f);
		size_t id = 0;
		for( auto i=decl->getVertices().begin(); i!=decl->getVertices().end(); i++ )
		{
			min = std::min( min, i->position );
			max = std::max( max, i->position );

			vertices[id] = i->position.x; 
			vertices[id+1] = i->position.y; 
			vertices[id+2] = i->position.z;
			id+=3;
			if( i->normal.second )
			{
				vertices[id] = i->normal.first.x; 
				vertices[id+1] = i->normal.first.y; 
				vertices[id+2] = i->normal.first.z;
				id+=3;
			}
			if( i->tangent.second )
			{
				vertices[id] = i->tangent.first.x; 
				vertices[id+1] = i->tangent.first.y; 
				vertices[id+2] = i->tangent.first.z;
				id+=3;
			}
			id++;
		}
		/* unlock the buffer */
		vertexBuffer->unlock();

		/* create the index buffer */
		Ogre::HardwareIndexBufferSharedPtr indexBuffer(Ogre::HardwareBufferManager::getSingleton().
			createIndexBuffer(Ogre::HardwareIndexBuffer::IT_16BIT, mMesh->sharedVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC));

		/* lock the buffer so we can get exclusive access to its data */
		uint16_t *indices = static_cast<uint16_t *>(indexBuffer->lock(Ogre::HardwareBuffer::HBL_NORMAL));
				
		id = 0;
		for( auto i=decl->getIndices().begin(); i!=decl->getIndices().end(); i++ )
		{
			/* define our triangle */
			indices[id] = id; id++;
		}
		/* unlock the buffer */
		indexBuffer->unlock();

		/* attach the buffers to the mesh */
		mMesh->sharedVertexData->vertexBufferBinding->setBinding(0, vertexBuffer);
		Ogre::SubMesh* subMesh = mMesh->createSubMesh();
		subMesh->useSharedVertices = true;
		subMesh->indexData->indexBuffer = indexBuffer;
		subMesh->indexData->indexCount = mMesh->sharedVertexData->vertexCount;
		subMesh->indexData->indexStart = 0;

		/* set the bounds of the mesh */
		mMesh->_setBounds(Ogre::AxisAlignedBox(min, max));

		/* notify the mesh that we're all ready */
		mMesh->load();

		decl.destroy();

		return mMesh;
	}
};

#endif